using System;
using System.Collections.Generic;
using Sop;
using Sop.Collections.BTree;

namespace EFCachingProvider.ScalableCache
{
    /// <summary>
    /// Batch container.
    /// </summary>
    internal class BatchContainer
    {
        /// <summary>
        /// Batch Types.
        /// </summary>
        internal enum BatchType
        {
            AddItems,
            AddSets,
            InvalidItems,
            InvalidSets
        };

        /// <summary>
        /// Total count of items of all batches in this Container.
        /// </summary>
        public int Count
        {
            get
            {
                return AddItems.Count + AddSets.Count + InvalidItems.Count + InvalidSets.Count;
            }
        }

        /// <summary>
        /// Items for add.
        /// </summary>
        public Dictionary<string, CacheEntry> AddItems = new Dictionary<string, CacheEntry>(BatchSize);
        /// <summary>
        /// Items for add entity sets.
        /// </summary>
        public Dictionary<string, Dictionary<string, byte>> AddSets = new Dictionary<string, Dictionary<string, byte>>(BatchSize);
        /// <summary>
        /// Invalid Items.
        /// </summary>
        public Dictionary<string, byte> InvalidItems = new Dictionary<string, byte>(BatchSize);
        /// <summary>
        /// Inbalid Items entity sets.
        /// </summary>
        public Dictionary<string, byte> InvalidSets = new Dictionary<string, byte>(BatchSize);

        /// <summary>
        /// Count of items to keep in-memory before they get flushed to disk.
        /// </summary>
        public static int BatchSize
        {
            get { return _batchSize; }
            set
            {
                if (value < 4)
                    throw new ArgumentException("BatchSize", "Minimum batch size is 4.");
                _batchSize = value;
            }
        }
        private static int _batchSize = 100;

        public void LockAll(OperationType operationType)
        {
            for (int i = 0; i < _locker.Length; i++)
                _locker[i].Lock(operationType);
        }
        public void UnlockAll(OperationType operationType)
        {
            for (int i = 0; i < _locker.Length; i++)
                _locker[i].Unlock(operationType);
        }

        public void Lock(BatchType[] batchType)
        {
            Lock(OperationType.Write, batchType);
        }
        public void Lock(OperationType operationType, BatchType batchType)
        {
            Lock(operationType, new[] {batchType});
        }
        public void Lock(OperationType operationType, BatchType[] batchType)
        {
            for (int i = 0; i < batchType.Length; i++)
                _locker[(int)batchType[i]].Lock(operationType);
        }
        public void Unlock(BatchType[] batchType)
        {
            Unlock(OperationType.Write, batchType);
        }
        public void Unlock(OperationType operationType, BatchType batchType)
        {
            Unlock(operationType, new[] { batchType });
        }
        public void Unlock(OperationType operationType, BatchType[] batchType)
        {
            for (int i = 0; i < batchType.Length; i++)
                _locker[(int)batchType[i]].Unlock(operationType);
        }

        /// <summary>
        /// Retrieves an item from the AddItem batch.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="entry"></param>
        /// <returns></returns>
        public bool GetItem(string key, out CacheEntry entry)
        {
            // try to get from AddItems...
            Lock(OperationType.Read, BatchType.AddItems);
            bool found = AddItems.TryGetValue(key, out entry);
            Unlock(OperationType.Read, BatchType.AddItems);
            return found;
        }

        ///// <summary>
        ///// Remove the expired Item from Store.
        ///// </summary>
        ///// <param name="entry"></param>
        //public void ExpireItem(string key, CacheEntry entry)
        //{
        //    //ExpiredItems[key] = entry.ExpirationTime;
        //}

        /// <summary>
        /// true means entry (or any of its dependent entity set) was marked as invalid, false otherwise.
        /// </summary>
        /// <param name="entry"></param>
        /// <returns></returns>
        public bool IsInvalidItem(CacheEntry entry)
        {
            Lock(OperationType.Read, BatchType.AddItems);
            if (entry.DependentEntitySets != null)
            {
                foreach (string s in entry.DependentEntitySets)
                {
                    if (!InvalidSets.ContainsKey(s)) continue;
                    Unlock(OperationType.Read, BatchType.AddItems);
                    return true;
                }
            }
            Unlock(OperationType.Read, BatchType.AddItems);
            return false;
        }
        /// <summary>
        /// true means one of the batches is full and thus, the batch
        /// will need to get mapped to disk, false otherwise.
        /// </summary>
        public bool IsFull
        {
            get
            {
                LockAll(OperationType.Read);
                bool f = AddItems.Count >= BatchSize ||
                       AddSets.Count >= BatchSize ||
                       InvalidItems.Count >= BatchSize ||
                       InvalidSets.Count >= BatchSize;
                UnlockAll(OperationType.Read);
                return f;
            }
        }
        /// <summary>
        /// true means all batches are empty, false otherwise.
        /// </summary>
        public bool IsEmpty
        {
            get
            {
                LockAll(OperationType.Read);
                bool f = AddItems.Count == 0 &&
                       AddSets.Count == 0 &&
                       InvalidItems.Count == 0 &&
                       InvalidSets.Count == 0;
                UnlockAll(OperationType.Read);
                return f;
            }
        }
        /// <summary>
        /// Copy batches from this container onto a target Container.
        /// </summary>
        /// <param name="target"></param>
        public void Copy(BatchContainer target)
        {
            LockAll(OperationType.Read);
            target.LockAll(OperationType.Write);
            CopyDictionary(AddItems, target.AddItems);
            CopyDictionary(AddSets, target.AddSets);
            CopyDictionary(InvalidItems, target.InvalidItems);
            CopyDictionary(InvalidSets, target.InvalidSets);
            target.UnlockAll(OperationType.Write);
            UnlockAll(OperationType.Read);
        }
        private static void CopyDictionary<TKey, TValue>(Dictionary<TKey, TValue> source, Dictionary<TKey, TValue> target)
        {
            target.Clear();
            foreach (var kvp in source)
                target.Add(kvp.Key, kvp.Value);
        }

        /// <summary>
        /// Clear the batches in this Container.
        /// </summary>
        public void Clear()
        {
            LockAll(OperationType.Write);
            AddItems.Clear();
            AddSets.Clear();
            InvalidItems.Clear();
            InvalidSets.Clear();
            UnlockAll (OperationType.Write);
        }

        private readonly Synchronizer[] _locker = new[]
                                                    {
                                                        new Synchronizer(),
                                                        new Synchronizer(),
                                                        new Synchronizer(),
                                                        new Synchronizer()
                                                    };
    }
}
